package com.hmall.search.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.common.utils.BeanUtils;
import com.hmall.search.domain.po.ItemDoc;
import com.hmall.search.domain.query.ItemPageQuery;
import com.hmall.search.domain.vo.PageVO;
import com.hmall.search.service.ISearchService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SearchServiceImpl implements ISearchService {

    private RestHighLevelClient client;
    private final static String INDEX_NAME = "items";

    @Autowired
    private ItemClient itemClient;

    public SearchServiceImpl() {
        client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.12.168:9200")));
    }

    @Override
    public void saveItemById(Long itemId) {

        try {
            //根据商品id查询商品并转换为itemDoc
            ItemDTO itemDTO = itemClient.queryItemById(itemId);
            ItemDoc itemDoc = BeanUtils.copyBean(itemDTO, ItemDoc.class);
            String itemDocJsonStr = JSONUtil.toJsonStr(itemDoc);
            //创建请求对象
            IndexRequest request = new IndexRequest(INDEX_NAME).id(itemId.toString());
            //设置参数
            request.source(itemDocJsonStr, XContentType.JSON);
            //发送请求
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("保存商品数据到es失败！" + e.getMessage());
        }

    }

    @Override
    public void deleteItemById(Long itemId) {
        try {
            DeleteRequest request = new DeleteRequest(INDEX_NAME).id(itemId.toString());
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("删除商品数据失败！" + e.getMessage());
        }
    }

    @Override
    public PageVO<ItemDoc> search(ItemPageQuery query) {
        try {
            //1、创建搜索请求对象
            SearchRequest searchRequest = new SearchRequest(INDEX_NAME);

            //2、设置请求对象的参数（搜索关键字、过滤、分页排序、高亮）
            boolean isHighlight = false;
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            if (StrUtil.isNotBlank(query.getKey())) {
                boolQueryBuilder.must(QueryBuilders.matchQuery("name", query.getKey()));
                //说明用户有输入搜索的关键字；需要对关键字进行高亮显示
                isHighlight = true;
            }
            //根据分类过滤
            if (StrUtil.isNotBlank(query.getCategory())) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("category", query.getCategory()));
            }
            //根据品牌过滤
            if (StrUtil.isNotBlank(query.getBrand())) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("brand", query.getBrand()));
            }
            //根据价格区间进行过滤
            if (query.getMinPrice() != null) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()));
            }
            if (query.getMaxPrice() != null) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(query.getMaxPrice()));
            }
            //设置排序
            if (StrUtil.isNotBlank(query.getSortBy())) {
                searchRequest.source().sort(query.getSortBy(), query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
            } else {
                //根据更新时间降序排
                //searchRequest.source().sort("updateTime", SortOrder.DESC);
            }
            //设置分页
            int pageNo = query.getPageNo() == null ? 1 : query.getPageNo();
            int pageSize = query.getPageSize() == null ? 20 : query.getPageSize();
            searchRequest.source().from((pageNo - 1) * pageSize).size(pageSize);

            //设置高亮
            if (isHighlight) {
                searchRequest.source().highlighter(SearchSourceBuilder.highlight()
                        .field("name")
                        .preTags("<font color='red'>")
                        .postTags("</font>")
                );
            }

            //searchRequest.source().query(boolQueryBuilder);
            //根据算分函数，按照广告进行加权算分
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                    boolQueryBuilder,
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    QueryBuilders.termQuery("isAD", true),
                                    //如果是isAD为true，则加权因子为10
                                    ScoreFunctionBuilders.weightFactorFunction(10)
                            )
                    }
            ).boostMode(CombineFunction.MULTIPLY);

            searchRequest.source().query(functionScoreQueryBuilder);

            //3、发送请求
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            //4、处理响应结果
            PageVO<ItemDoc> pageVO = new PageVO<>();
            SearchHits searchHits = searchResponse.getHits();

            //设置总记录数
            long total = searchHits.getTotalHits().value;
            pageVO.setTotal(total);
            //设置总页数
            long pages = (long) (total % pageSize == 0 ? (total / pageSize) : (total / pageSize + 1));
            pageVO.setPages(pages);
            //记录列表
            List<ItemDoc> itemDocList = new ArrayList<>(pageSize);

            SearchHit[] hits = searchHits.getHits();
            if (hits != null) {
                for (SearchHit hit : hits) {
                    String itemDocJsonStr = hit.getSourceAsString();
                    ItemDoc itemDoc = JSONUtil.toBean(itemDocJsonStr, ItemDoc.class);
                    if (isHighlight) {
                        //处理高亮标题
                        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                        if (highlightFields != null && highlightFields.containsKey("name")) {
                            HighlightField highlightField = highlightFields.get("name");
                            String highlightName = highlightField.getFragments()[0].string();
                            itemDoc.setName(highlightName);
                        }
                    }

                    itemDocList.add(itemDoc);
                }
            }

            pageVO.setList(itemDocList);
            return pageVO;
        } catch (IOException e) {
            throw new RuntimeException("搜索es中的商品失败！" + e.getMessage());
        }
    }

    @Override
    public Map<String, List<String>> filters(ItemPageQuery query) {

        try {
            //1、创建搜索请求对象
            SearchRequest searchRequest = new SearchRequest(INDEX_NAME);

            //2、设置请求对象的参数（搜索关键字、过滤、分页排序、高亮）
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            if (StrUtil.isNotBlank(query.getKey())) {
                boolQueryBuilder.must(QueryBuilders.matchQuery("name", query.getKey()));
            }
            //根据分类过滤
            boolean isNeedCategory = true;
            if (StrUtil.isNotBlank(query.getCategory())) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("category", query.getCategory()));
                isNeedCategory = false;
            }
            //根据品牌过滤
            boolean isNeedBrand = true;
            if (StrUtil.isNotBlank(query.getBrand())) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("brand", query.getBrand()));
                isNeedBrand = false;
            }
            //根据价格区间进行过滤
            if (query.getMinPrice() != null) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()));
            }
            if (query.getMaxPrice() != null) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(query.getMaxPrice()));
            }
            //只需要根据分类、品牌分组的结果；并不需要返回文档（商品数据）
            searchRequest.source().size(0);

            searchRequest.source().query(boolQueryBuilder);

            //设置分类聚合
            if (isNeedCategory) {
                searchRequest.source().aggregation(AggregationBuilders.terms("categoryAgg").field("category").size(20));
            }
            //设置品牌聚合
            if (isNeedBrand) {
                searchRequest.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(20));
            }

            //3、发送请求
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

            //4、处理响应结果
            Map<String, List<String>> resultMap = new HashMap<>();
            Aggregations aggregations = searchResponse.getAggregations();

            //处理分类分组结果
            if (aggregations != null) {
                Terms categoryAgg = aggregations.get("categoryAgg");
                if (categoryAgg != null) {
                    List<? extends Terms.Bucket> buckets = categoryAgg.getBuckets();
                    List<String> categoryList = new ArrayList<>(buckets.size());
                    for (Terms.Bucket bucket : buckets) {
                        categoryList.add(bucket.getKeyAsString());
                    }
                    resultMap.put("category", categoryList);
                }
            }
            //处理品牌分组结果
            if (aggregations != null) {
                Terms brandAgg = aggregations.get("brandAgg");
                if (brandAgg != null) {
                    List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
                    List<String> brandList = new ArrayList<>(buckets.size());
                    for (Terms.Bucket bucket : buckets) {
                        brandList.add(bucket.getKeyAsString());
                    }
                    resultMap.put("brand", brandList);
                }
            }


            return resultMap;
        } catch (IOException e) {
            throw new RuntimeException("对es中搜索的商品分组失败！" + e.getMessage());
        }
    }
}
